package com.gc.cscott.cli;

import com.gc.cscott.domain.Competitor;
import com.gc.cscott.fs.FileSystemWorker;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;

/**
 *
 * @author root
 */
public class FindCheatersCommand implements Command {
    private static final String keyMd5Full = "md5";
    private static final String keyMd5Tail = "md5tail";
    private static final int tailPieceSize = 300;

    private static final String keyDiff = "diff";

    private static final String keyCountBrackets = "cb";

    @Inject
    FileSystemWorker fsWorker;

    public List<String> listHelp() {
        return Arrays.asList(
                "Will find cheaters using some nested techniques.",
                "Use key:",
                "  "+keyMd5Full + " to find same checksum files.",
                "  '" + keyMd5Tail + "' to find files with same checksum of last "+tailPieceSize+" bytes of file.",
                "  '" + keyDiff + "' to find nearest by diff files.",
                "  '" + keyCountBrackets + "' to find files with same (){}<> relation positions.");
    }

    public List<String> perform(List<String> pendingArgs) {
        try {
            if (pendingArgs.isEmpty() == true) {
                throw new Error("Algorithm type is not specified, use help for details.");
            } else {
                String algorithm = pendingArgs.get(0);
                if (keyMd5Tail.equals(algorithm)==true){
                    performSearch(new ChecksumProvider() {
                        public String checksum(byte[] fileBytes) {
                            if (fileBytes.length>tailPieceSize){
                                byte[] tailBytes = new byte[tailPieceSize];
                                for(int i=0;i<tailBytes.length;i++){
                                    tailBytes[i] = fileBytes[fileBytes.length-tailBytes.length+i];
                                }
                                return DigestUtils.md5Hex(tailBytes);
                            } else {
                                return DigestUtils.md5Hex(fileBytes);
                            }
                        }

                        public String formatChecksum(String checksum) {
                            return checksum;
                        }
                    });
                } else if (keyMd5Full.equals(algorithm) == true) {
                    performSearch(new ChecksumProvider() {
                        public String checksum(byte[] fileBytes) {
                            return DigestUtils.md5Hex(fileBytes);
                        }

                        public String formatChecksum(String checksum) {
                            return checksum;
                        }
                    });
                } else if (keyCountBrackets.equals(algorithm) == true) {
                    performSearch(new ChecksumProvider() {
                        public String checksum(byte[] fileBytes) {
                            StringBuilder checksum = new StringBuilder();
                            for(final byte b:fileBytes){
                                final char ch = (char) b;
                                if (ch=='('||ch==')'||ch=='<'||ch=='>'||ch==';'){
                                    checksum.append(ch);
                                }
                            }
                            return checksum.toString();
                        }

                        public String formatChecksum(String checksum) {
                            return StringUtils.substring(checksum, 0, 32);
                        }
                    });
                } else if (keyDiff.equals(algorithm) == true) {
                    throw new UnsupportedOperationException(algorithm + " algorithm is not yet implemented");
                } else {
                    throw new Error("Unknown algorithm: " + algorithm + ", use help for available options.");
                }
                return ImmutableList.copyOf(pendingArgs.subList(1, pendingArgs.size()));
            }
        } catch (IOException ex) {
            throw new Error(ex);
        }

    }

    private void performSearch(ChecksumProvider checksumProvider) throws IOException {
        ChecksumToFilesMap digestsMap = new ChecksumToFilesMap();
        for (Competitor competitor : fsWorker.listCompetitorsAll()) {
            for (File dir : competitor.getBasedir().listFiles()) {
                if (dir.isDirectory() == true) {//getting into solution directory.
                    for (File file : dir.listFiles()) { //getting all files from solution directory.
                        if (file.isFile() == true) {
                            byte[] fileBytes = FileUtils.readFileToByteArray(file);
                            String checksum = checksumProvider.checksum(fileBytes);
                            digestsMap.putDigest(checksum, file);
                        }
                    }
                }
            }
        }
        System.out.println("Duplicates by checksum:");
        int duplicatesGroupCount = 0;
        for (Map.Entry<String, List<File>> entry : digestsMap.entrySet()) {
            if (entry.getValue().size() > 1) {
                duplicatesGroupCount++;
                for (File file : entry.getValue()) {
                    System.out.printf("%s - %s\n", checksumProvider.formatChecksum(entry.getKey()), file.getAbsolutePath());
                }
                System.out.println("");
            }
        }
        System.out.println("Total groups: " + duplicatesGroupCount);
    }

    private interface ChecksumProvider {
        String checksum(byte[] fileBytes);

        String formatChecksum(String checksum);
    }

    private static class ChecksumToFilesMap extends TreeMap<String, List<File>> {
        public void putDigest(String digest, File file) {
            if (containsKey(digest) == false) {
                put(digest, new ArrayList<File>());
            }
            get(digest).add(file);
        }
    }
}
